面向 AI / Agent 友好的 CLI 开发建议
Apr 18, 2026笔记ai
面向 AI / Agent 友好的 CLI 开发建议
过去 CLI 主要是给”人”用的:人会读帮助文档、会脑补上下文、会容忍一点输出格式不稳定、甚至会在报错时自己猜下一步。
但进入 AI / Agent 时代后,CLI 的一个新用户出现了:大模型驱动的自动化执行体。
它和人类用户最大的不同在于:
- 它不是”看起来能用”就行,而是要可稳定解析
- 它不是”偶尔报错再看一下”,而是要可预测地恢复
- 它不是只会点一个命令,而是会把 CLI 当成工具链节点接到更大工作流里
- 它不是读完 README 再上手,而是倾向于通过
--help、--json、退出码、子命令结构来即时建立心智模型 - 它的上下文窗口是稀缺资源:每一次冗余输出都在烧 token
所以,今天一个优秀的 CLI,已经不只是”developer-friendly”,还应该是 agent-friendly。
一、核心判断:AI 时代,CLI 正在从”终端入口”变成”系统接口”
越来越多 Agent 系统并不是直接调用 SDK 或 MCP,而是优先复用 shell 中已经存在的 CLI 工具。原因很直接:
- CLI 天然具备命令组合能力,可以通过 pipe、重定向、shell script 进入复杂工作流。
- CLI 通常已经有
--help、参数体系、退出码等约定,Agent 无需额外接入专有协议就能调用。 - 针对简单任务,直接 CLI 往往比引入完整 tool schema 更省上下文成本。
这意味着一个很重要的设计转向:
不要再把 CLI 仅仅视作”人类操作界面”,而要把它当成”面向 Agent 的文本 API”。
CLI 与 MCP:不是二选一
一个常见误区是:”有了 MCP,CLI 就过时了。”实际上两者的定位不同:
| 维度 | CLI | MCP Server |
|---|---|---|
| 接入成本 | Agent 原生会用 shell | 需要 host 支持 MCP |
| 上下文成本 | 低(文本即协议) | 通常更高(tool schema 常驻) |
| 类型安全 | 弱(靠 JSON 输出约束) | 强(schema 驱动) |
| 人类可用性 | 高 | 低(几乎只给 Agent 用) |
| 发布与分发 | 成熟(brew/apt/单二进制) | 仍在演进 |
合理的产品策略通常是:先做好 agent-friendly CLI,再在此基础上薄薄包一层 MCP。 CLI 是 source of truth,MCP 只是”更贵但更结构化”的壳。反过来做通常会付出额外的维护代价。
二、第一原则:stdout 是机器通道,stderr 是人类通道
这是最值得被写进规范的一条。
对 Agent 来说,最理想的契约不是”输出尽量清晰”,而是:
stdout:只放机器可消费的数据stderr:只放提示、日志、警告、进度、解释性文本- 退出码:表达最终状态
❌ 反例
1 | $ mycli resource get |
Agent 必须写正则去剥离首尾噪音,且任何一次日志格式微调都会让解析失败。
✅ 正例
1 | $ mycli resource get --json |
即使 stderr 里有日志,stdout > data.json 也能保证是纯 JSON。
工程纪律
- 没有例外:进度、spinner、”Press any key”、彩色提示全部走 stderr。
- TTY 检测:
isatty(stdout)为 false 时,默认关闭彩色与动画(NO_COLOR 公约见第九节)。 --quiet是要求 stderr 静默,不是关闭 stdout 数据。--verbose只增加 stderr 信息量,绝不污染 stdout。
三、默认提供 --json,而且把 JSON 当正式接口来维护
不是”能输出 JSON”就够了,而是要像对待 HTTP API 那样对待它:稳定、完整、版本化、可演进。
3.1 所有会返回结果的命令都支持 --json
不仅是 list,get / create / update / delete / status / validate / diff / logs 全部都该支持。
3.2 结构稳定,不要把 JSON 当日志快照
❌ 反例
1 | { "message": "User created successfully" } |
Agent 得继续猜:ID 呢?状态呢?真的成功了吗?
✅ 正例
1 | { |
3.3 空值也返回结构,不要语义漂移
❌ 反例:一会儿 []、一会儿 {}、一会儿 "No results"。
✅ 正例
1 | { "ok": true, "data": [], "meta": { "count": 0 } } |
3.4 错误也返回结构化 JSON
1 | { |
Agent 可以基于 code 精确分支,比读自然语言可靠得多。
3.5 Schema 版本化是必修课
一旦 --json 成为接口,任何字段重命名都是破坏性变更。
- 在
meta.schema_version中声明版本。 - 破坏性变更走
--json-version=v2,旧版继续兼容至少一个大版本。 - 新增字段永远是兼容的;删除或重命名字段永远是破坏性的。
3.6 时间、数字、枚举一律机器友好
- 时间:ISO-8601 UTC(
2026-04-21T09:30:00Z),不要 “2 days ago”、”yesterday”。 - 数字:裸数字,不带千分位、货币符号、locale 分隔符。
- 枚举:小写下划线 + 闭集合(
active/failed/pending),在--help或mycli status --list-values可枚举。
四、【新增】流式输出:长任务用 NDJSON,别让 Agent 干等
“stdout 纯 JSON” 的原则遇到长任务(构建、部署、训练、日志拉取)会不够用——一次性吐一个 JSON 意味着过程中 Agent 什么都看不到。
更好的做法是 NDJSON(Newline-Delimited JSON):stdout 每行是一个独立 JSON 事件。
1 | $ mycli release deploy --env prod --json |
好处:
- Agent 可以一边读一边做决策(例如看到某一步失败立即取消后续)。
- 人类也能跟进度(每行是自然的日志)。
- 容器/CI 日志采集器天然友好。
设计要点
- 最后一行必须是终态事件(
type: result或type: error),让消费者知道什么时候停。 - 每条事件都带
ts(ISO-8601 UTC),方便后排序/去重。 - 如果你同时需要”单 JSON 模式”给简单消费者,提供
--json=single/--json=stream二选一。
❌ 反例
把进度信息用文本夹在 JSON 前后:
1 | Building... |
这对人类看着没毛病,对 Agent 是灾难。
五、【新增】输出体积与上下文预算:别把 Agent 的窗口塞爆
这是面向 Agent 最容易被忽略、却最影响实际效果的一点。 一个 kubectl get pods -A -o json 动辄几 MB,塞进上下文等于直接爆窗口。
设计要点
默认截断 + 显式分页
1
2
3
4
5{
"ok": true,
"data": [ /* 最多 50 条 */ ],
"meta": { "count": 50, "total": 12345, "truncated": true, "next_cursor": "eyJvZmZzZXQiOjUwfQ==" }
}--limit和--cursor是一对组合拳。字段投影:
--fields id,name,status让 Agent 只要它要的列,可以减少 80% 以上的 token。过滤优先于分页:提供
--filter status=failed --since 7d,比让 Agent 拉全量再过滤省得多。摘要模式:
--summary只返回计数、分组等聚合结果。明确告知被截断:永远不要沉默地丢数据,必须有
truncated: true之类的信号。
❌ 反例
一条命令返回 200MB JSON,没有分页,也没有截断提示。Agent 要么 OOM 要么被迫上 head/jq 做二次加工,而 head 截断会破坏 JSON 语法。
六、退出码要”可编排”,不是只有成功/失败
人类用户经常只看一眼报错文案;Agent 更依赖退出码来决定下一步。
退出码不是礼节,而是状态机接口。
建议分层(参考 BSD sysexits.h 64–78)
| 码 | 含义 | Agent 典型响应 |
|---|---|---|
| 0 | 成功 | 继续 |
| 1 | 通用失败(兜底) | 查 stderr |
| 2 | 参数错误(EX_USAGE=64) |
重新读 --help |
| 3 | 认证失败(EX_NOPERM=77) |
检查 env / 重新登录 |
| 4 | 资源不存在 | 跳过或重建 |
| 5 | 权限不足 | 转人工 |
| 6 | 网络/临时失败 | 指数退避重试 |
| 7 | 冲突,需人工确认 | 转人工或加 --force |
| 8 | 前置依赖未满足 | 先装依赖/先建资源 |
注:具体数值可以沿用 sysexits 的 64+,也可以用上表的 2–8。关键不是数字,而是团队内一致 + 每个码文档化 + mycli --help 里写清楚。
❌ 反例
无论什么错都退出 1,Agent 只能靠匹配 stderr 文本来分支——一次文案改动就能打爆它。
七、帮助信息要让 Agent 快速建模
CLI 对 Agent 最大的优势之一,是天然具备自描述能力:--help、子命令层级、参数说明,本质上都是工具的即时 schema。
7.1 层级清晰
1 | mycli --help |
让 Agent 能逐层探索,而不是一次性暴露 2000 行帮助。
7.2 帮助文案里明确写出输入/输出契约
1 | Output: |
7.3 示例优先
每个高频子命令至少给一条最短成功路径:
1 | # Create a project |
7.4 可枚举入口
1 | mycli --list # 所有顶层命令 |
7.5 Shell completion 是 Agent 友好的副产品
为 bash/zsh/fish 生成补全(mycli completion bash),本质上给 Agent 也提供了”有哪些参数可选”的 source of truth——很多 Agent harness 已经会读补全脚本。
八、【原第六节,升级】AGENTS.md:从命令级 discoverability 到任务级 strategy
社区事实标准正在向 AGENTS.md 收敛(Cursor、Aider、Zed、OpenAI Codex 等都在读它);Anthropic 的 Skills 是另一套机制。CLI 作者可以提供以下三层中的任意一层或多层:
AGENTS.md(推荐起步):随仓库分发的 Agent 使用手册。mycli agent-prompt命令:打印一段可直接注入 Agent 系统提示的精简说明。- MCP server:结构化的工具封装。
--help 告诉 Agent:这个命令怎么调;AGENTS.md 告诉 Agent:遇到某类任务,应该先调哪个命令、再调哪个命令、哪些坑要避开。
推荐结构
1 | # MyCLI Agent Guide |
CLI 的未来:不只是有帮助文档,而是能主动”教会 Agent 怎么用自己”。
九、交互模式优雅,非交互模式绝对可靠
传统 CLI 很喜欢:首次运行弹登录、删除前 Are you sure?、参数缺失进表单、spinner、Press any key——对人类友好,对 Agent 是灾难。
9.1 严格区分两种模式
| 交互(TTY) | 非交互(CI/Agent) | |
|---|---|---|
| 彩色 | ✅ | ❌(遵守 NO_COLOR) |
| 进度条/spinner | ✅ | ❌(改用 NDJSON 事件) |
| 二次确认弹窗 | ✅ | ❌(需要时显式要 --yes) |
| 参数缺失进表单 | ✅ | ❌(直接退 2) |
| 首次运行登录向导 | ✅ | ❌(报错并提示 env 变量) |
判断依据:isatty(stdin) && isatty(stdout) + 环境变量 CI / MYCLI_NON_INTERACTIVE。
9.2 遵守业界公约
NO_COLOR环境变量(no-color.org)存在即关闭所有 ANSI 色彩,不论 TTY。TERM=dumb同样应关闭彩色与光标控制。- SIGPIPE 要优雅处理:上游
| head -n 10被关闭管道时不要报错退出。
十、安全分级要编码进命令语义
面向 Agent 的 CLI,安全不能只靠 README 里的”请谨慎使用”。
分级
- read:查询、列举、预览、diff。
- write:创建、更新、触发执行。
- dangerous:删除、覆盖、清空、支付、发布到生产。
配套机制
read:直接执行。write:需--yes(非交互场景)。dangerous:需--force,关键操作再加--confirm <resource-id>要求 Agent 复述 ID。
1 | mycli config update --file prod.yaml --yes |
参考 kubectl delete 与 terraform destroy:前者用 --force --grace-period=0,后者强制交互确认除非 -auto-approve——都是把风险编码进参数的好例子。
让 Agent 具备可验证的安全边界。很多系统失败,并不是模型不会推理,而是工具给了它”过度自由”。
十一、命令应该幂等、可重试、可预演
Agent 会天然地重试。只要你给它一个”可能是临时失败”的信号,它大概率会再次执行。
11.1 幂等 key / 去重语义
1 | mycli invoice create --request-id req_20260421_abc --json |
服务端用 request_id 去重 24h,第二次调用返回同一结果而不是创建新记录。
11.2 --dry-run
1 | mycli release deploy --env prod --dry-run --json |
11.3 plan / apply 二阶段(参考 Terraform)
1 | mycli infra plan --out plan_123.json |
把”理解变化”和”执行变化”拆开,Agent 可以先读 plan、让人类或另一个模型审一遍,再 apply。
11.4 显式标注错误是否可重试
1 | { |
十二、资源标识与时间要机器友好
❌ 反例
1 | NAME STATUS CREATED |
问题:相对时间无法稳定比较;名称可能不唯一;列宽表格难以解析。
✅ 正例
1 | { |
纪律
- 每个资源都有稳定、URL-safe、带前缀的 ID(
prj_、usr_、rel_),便于 Agent 一眼识别类型。 - 时间一律 ISO-8601 UTC,不受
LC_ALL/ 时区影响。 - 状态是闭集合枚举。
- 任何对名称的操作都要支持用 ID 替代(
mycli project get --id prj_123而不是只能--name)。
十三、参数命名直白,凭证走安全通道
13.1 参数命名
❌ 反例:tool do -x -m fast
✅ 正例:tool convert --input in.md --output out.html --mode fast
规则:
- 少用无语义短参数(
-h/-v除外)。 - 长参数优先,与领域模型命名一致。
- 枚举值可预测、可 discover。
- 同一个参数在不同子命令下语义不要漂移。
判断标准:脱离文档时,模型能不能大致猜中用途?
13.2 凭证注入:三种方式的优先级
| 方式 | 推荐度 | 说明 |
|---|---|---|
环境变量(MYCLI_TOKEN) |
⭐⭐⭐ | CI/Agent 默认 |
stdin(--token-stdin) |
⭐⭐⭐ | 避免进 shell history |
配置文件(~/.mycli/config) |
⭐⭐ | 人类日常使用 |
命令行参数(--token xxx) |
❌ | 会进 ps/history,不要做 |
并且:
- 永远不要把 token 回显到 stdout 或 stderr(包括
--verbose模式)。 - 错误信息里暴露 token 前 4 位用于调试即可。
- 支持
mycli auth whoami --json让 Agent 自检当前身份。
十四、环境可移植性:别让 Agent 死在依赖上
Agent 对环境脆弱性的容忍度远低于人类开发者——人类会手动修,Agent 往往只会”再试一次”。
优先目标:
- 单文件分发(Go/Rust 的天然优势;Python 可用 PyInstaller / uv 单二进制)。
- 最少运行时依赖、无需人工激活虚拟环境。
mycli --version输出 semver + commit hash + build date。mycli doctor自检:依赖、认证、网络、权限一次性扫描并结构化报告。- 缺失依赖时给结构化错误 + 修复建议,不要只打印
ImportError。
1 | $ mycli doctor --json |
十五、把 CLI 当产品来做评测
工具质量不只靠单元测试,还要靠系统化评测——尤其是让真实 Agent 去跑一遍。
三层评测
1)语法层:参数一致性、--help 完整性、所有核心命令 --json 覆盖、错误码稳定。
2)契约层:stdout/stderr 不混、JSON schema 无漂移、non-TTY 不阻塞、错误可恢复、NDJSON 终态事件必有。
3)Agent 实战层:给真实 Agent 一组任务,统计指标。
最小评测脚本(Agent 实战层)
1 | # eval_suite.yaml |
每次发版跑一遍,退化立即可见。
十六、【新增】存量 CLI 的渐进式改造路径
大部分读者不是新写 CLI,而是改老的。一口气重写成本过高,建议按下列顺序 retrofit(每步都能单独发布、独立收益):
- Phase 0(1 天):确认 stdout/stderr 纪律,把所有日志挪到 stderr。
- Phase 1(1 周):核心 3–5 个命令加
--json,字段按{ok, data, meta}结构。 - Phase 2(1 周):错误走结构化 JSON + 退出码分层,补
retryable字段。 - Phase 3(2 周):长任务改造成 NDJSON;加分页、字段投影、
--limit。 - Phase 4(1 周):非交互模式严格化(NO_COLOR、
--yes、不弹任何向导)。 - Phase 5(持续):写
AGENTS.md、建评测集、每版本跑一遍。 - Phase 6(可选):MCP server 薄壳封装。
反模式:一次性推倒重来、把所有命令都加 --json 但 schema 不稳定、AGENTS.md 写了但不跟代码同步演进。
十七、【新增】真实 CLI 案例点评
学习最快的方式是看业界做对和做错的地方。
kubectl:-o json/-o yaml/-o jsonpath是典范;缺点是kubectl apply的输出一会儿人类文本一会儿结构化,非-o场景 stderr/stdout 也有混用。gh(GitHub CLI):--json field1,field2字段投影做得极好,极大压缩 Agent 上下文;gh api暴露底层 REST 兜底;AGENTS.md式引导欠缺。stripeCLI:--api-key和 env var 组合清晰,stripe events resume幂等支持好;错误 JSON 结构稳定。terraform:plan/apply二阶段、-auto-approve、-json流式输出是 Agent 友好模板级案例。awsCLI v2:--output json默认结构化、分页--max-items/--starting-token完善;但帮助文档过长、子命令数量巨大,Agent 首次建模成本高——提醒我们 discoverability 要配合AGENTS.md裁剪。docker:历史包袱重,docker ps默认输出表格且列不稳定,--format '\{\{json \.\}\}'是事实标准但不直观——反面教材:JSON 不该是隐藏档。npm/pnpm:--json覆盖不全、错误信息人机混排是典型反例。
十八、最容易被忽略的一点
很多人以为”面向 Agent 友好 = 多加个 --json“。
真正让人眼前一亮的 CLI,不是”让 Agent 能调”,而是:
它会主动降低 Agent 的推理负担。
具体表现:
- 不让 Agent 猜输出格式 → 稳定 JSON schema + 版本号
- 不让 Agent 猜下一步该调哪个命令 →
AGENTS.md+ workflow 示例 - 不让 Agent 猜错误是否可重试 →
retryable字段 - 不让 Agent 猜这个操作是不是危险 → read/write/dangerous 分级 +
--confirm - 不让 Agent 猜资源标识该怎么取 → 稳定 ID + ISO 时间
- 不让 Agent 猜非交互模式会不会卡住 → 严格的 TTY 检测与退出纪律
- 不让 Agent 猜输出会不会爆窗口 → 默认截断 + 分页 + 字段投影
把复杂性消解在工具设计里,而不是丢给模型。 这是 AI 时代 CLI 设计真正的升级方向。
十九、Agent-Friendly CLI 自查清单(打印贴墙版)
输出契约
- stdout 只输出数据,stderr 只输出日志/提示
- 所有核心命令支持
--json - JSON 采用
{ok, data, meta}或等价的稳定结构 -
meta.schema_version字段存在 - 空值返回结构而非
"No results" - 长任务支持 NDJSON(
--json=stream)并以终态事件收尾 - 时间全部 ISO-8601 UTC
- 资源有稳定、带前缀的 ID
错误处理
- 退出码分层(至少区分:参数错 / 认证 / 找不到 / 临时失败 / 冲突)
- 错误以 JSON 返回,带
code/message/hint/retryable - 退出码在
--help中文档化
体积与性能
- 列表命令默认
--limit,支持--cursor分页 - 支持
--fields字段投影 - 支持
--filter/--since等过滤 - 被截断时有
truncated: true信号
交互 vs 非交互
- TTY/non-TTY 行为明确区分
- 遵守
NO_COLOR公约 - 非交互模式下绝不等待输入
- 需要确认的操作有
--yes/--force
安全
- 命令按 read/write/dangerous 分级
- 危险操作需
--confirm <resource-id>复述 - 凭证不走命令行参数,不回显到任何输出
Discoverability
- 层级化
--help,每个子命令都有 - 帮助里写明 stdout/stderr/exit code 契约
- 每个高频命令有可直接运行的 example
- 提供 shell completion
- 有
mycli doctor自检 - 有
mycli --version含 commit hash
Agent 文档
- 仓库有
AGENTS.md - 包含 workflow、invariants、gotchas、风险表
- 有 Agent 评测集,每版本跑一次
工程可移植性
- 单文件分发或极少依赖
- 缺失依赖给结构化错误与修复建议
二十、结语:CLI 正在变成”可组合的文本协议”
在 GUI 时代,软件的核心竞争力常常体现在界面。
但在 Agent 时代,很多系统首先被消费的,不再是界面,而是:
- 命令结构
- 输出契约
- 错误语义
- 权限边界
- 帮助系统
- 可组合性
从这个意义上说,CLI 正在重新变得重要——不是因为大家回到了终端,而是因为 Agent 天生适合终端。
所以面向 AI / Agent 开发 CLI,最值得记住的一句话是:
把你的 CLI 当成一个给模型调用的 API 来设计,而不是一个给人临时敲一下的命令。
这样做,CLI 才会从”脚手架工具”进化成”Agent 基础设施”。
附录:延伸阅读
- clig.dev — 经典 Command Line Interface Guidelines
- no-color.org —
NO_COLOR环境变量公约 - BSD
sysexits.h— 退出码约定 - AGENTS.md / llms.txt — Agent 时代兴起的仓库级元数据约定
- Terraform plan/apply、Kubernetes
kubectl -o json、GitHubgh --json— 值得研究的工业级 agent-friendly 实现
变更记录(v1.0 → v1.1)
- 新增:CLI 与 MCP 的关系对比(第一节)
- 新增:NDJSON 流式输出章节(第四节)
- 新增:输出体积与 token 预算章节(第五节)
- 新增:凭证注入安全通道(第十三节)
- 新增:存量 CLI 渐进式改造路径(第十六节)
- 新增:真实 CLI 案例点评(第十七节)
- 新增:自查清单(第十九节)
- 扩充:退出码章节补上 sysexits.h 引用
- 重命名:
SKILL.md→AGENTS.md,补充社区标准说明 - 补充:NO_COLOR / SIGPIPE / shell completion 业界公约
- 每节尽量补充”❌ 反例 / ✅ 正例”对照
- 调整:原 15、16 节合并精简为第十八、二十节
Author
My name is Micheal Wayne and this is my blog.
I am a front-end software engineer.
Contact: michealwayne@163.com